// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jshin

#pragma once

#include <condition_variable>
#include <mutex>
#include <queue>
#include <thread>
#include <utility>

template <typename T>
class Queue {
 public:
  T pop() {
    std::unique_lock<std::mutex> mlock(mutex_);
    while (queue_.empty()) {
      cond_.wait(mlock);
    }
    auto item = queue_.front();
    queue_.pop();
    return item;
  }

  void pop(T* item) {
    std::unique_lock<std::mutex> mlock(mutex_);
    while (queue_.empty()) {
      cond_.wait(mlock);
    }
    *item = queue_.front();
    queue_.pop();
  }

  bool try_pop(T* item) {
    std::unique_lock<std::mutex> mlock(mutex_);
    while (queue_.empty()) {
      return false;
    }
    *item = queue_.front();
    queue_.pop();
    return true;
  }

  void push(const T& item) {
    std::unique_lock<std::mutex> mlock(mutex_);
    queue_.push(item);
    mlock.unlock();
    cond_.notify_one();
  }

  void push(T&& item) {
    std::unique_lock<std::mutex> mlock(mutex_);
    queue_.push(std::move(item));
    mlock.unlock();
    cond_.notify_one();
  }

  size_t Size() {
    std::unique_lock<std::mutex> mlock(mutex_);
    return queue_.size();
  }

 private:
  std::queue<T> queue_;
  std::mutex mutex_;
  std::condition_variable cond_;
};
